package com.ztgf.order.common.config;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Date;

/**
 * <p>
 */
@Slf4j
public class CommonIdGenerator {
    // 以 2017-01-01 00:00:00 为起始时间点
    public static final long EPOCH;
    // 自增ID 16位
    private static final long SEQUENCE_BITS = 16L;
    // 机器IP 16位
    private static final long WORKER_IP_BITS = 16L;
    // IDC机房 2位
    private static final long IDC_BITS = 2L;

    private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;

    // IP向左移 16位
    private static final long WORKER_IP_LEFT_SHIFT_BITS = SEQUENCE_BITS;
    // IDC向左移32位
    private static final long IDC_LEFT_SHIFT_BITS = WORKER_IP_LEFT_SHIFT_BITS + WORKER_IP_BITS;
    // 时间向左移34位
    private static final long TIMESTAMP_LEFT_SHIFT_BITS = IDC_LEFT_SHIFT_BITS + IDC_BITS;

    private static final int IDC_MAX_VALUE = ~(-1 << IDC_BITS);


    private static long workerIP;

    private static long idc;

    private static final SecureRandom RANDOM = new SecureRandom();

    private static long sequence = 0L;

    private static DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC);

    private long lastTime;

    static {

        LocalDateTime dateTime = LocalDateTime.of(2020, 1, 1, 0, 0, 0, 0);
        EPOCH = dateTime.atZone(ZoneOffset.UTC).toInstant().getEpochSecond();

        InetAddress address;
        try {
            address = InetAddress.getLocalHost();
        } catch (final UnknownHostException e) {
            throw new IllegalStateException("Cannot get LocalHost InetAddress, please check your network!");
        }
        byte[] ipAddressByteArray = address.getAddress();

        workerIP = (long) (((ipAddressByteArray[ipAddressByteArray.length - 2] & 0xFF) << Byte.SIZE) + (ipAddressByteArray[ipAddressByteArray.length - 1] & 0xFF));

        final String dataCenter = System.getProperty("project.datacenter.id", "0");

        try {
            final int _idc = Integer.parseInt(dataCenter);
            if (_idc < 0 || _idc > IDC_MAX_VALUE) {
                throw new IllegalArgumentException(String.format("project.datacenter.id is error,must >= 0 , <=%s", IDC_MAX_VALUE));
            }
            idc = _idc;
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("project.datacenter.id is error, must be int type.");
        }

    }

    private static volatile CommonIdGenerator instance = null;

    private CommonIdGenerator() {
    }


    public static CommonIdGenerator getInstance() {
        CommonIdGenerator sInst = instance;
        if (sInst == null) {
            synchronized (CommonIdGenerator.class) {
                sInst = instance;
                if (sInst == null) {
                    sInst = new CommonIdGenerator();
                    instance = sInst;
                }
            }
        }
        return sInst;
    }

    /**
     * 生成Id.
     *
     * @return 返回@{@link Long}类型的Id
     */
    protected synchronized long generateId() {
        long time = epochSeconds();
        if (lastTime > time) {
            throw new IllegalStateException(String.format("Clock is moving backwards, last time is %d seconds, current time is %d seconds", lastTime, time));
        }
        if (lastTime == time) {
            if (0L == (sequence = ++sequence & SEQUENCE_MASK)) {
                time = waitUntilNextTime(time);
                sequence = RANDOM.nextInt(10);
            }
        } else {
            sequence = RANDOM.nextInt(10);
        }
        lastTime = time;
        if (log.isDebugEnabled()) {
            final String stamp = DATETIME_FORMATTER.format(Instant.ofEpochSecond(lastTime));
            log.debug("{}-{}-{}", stamp, workerIP, sequence);
        }
        return ((time - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (idc << IDC_LEFT_SHIFT_BITS) | (workerIP << WORKER_IP_LEFT_SHIFT_BITS) | sequence;
    }


    protected String unpack(long id) {
        long _ip = id >> WORKER_IP_LEFT_SHIFT_BITS & 0xffff;
        final String ip = String.format("%s.%s", (_ip >> Byte.SIZE) & 0xFF, _ip & 0xFF);
        long time = id >> TIMESTAMP_LEFT_SHIFT_BITS;
        return String.format("%s-%s-%s-%s", DATETIME_FORMATTER.format(Instant.ofEpochSecond(time + EPOCH)),
                (id >> IDC_LEFT_SHIFT_BITS) & 0x03,
                ip,
                id & 0xffff);
    }

    private long waitUntilNextTime(final long lastTime) {

        long time = epochSeconds();
        while (time <= lastTime) {
            time = epochSeconds();
        }
        return time;
    }

    private long epochSeconds() {
        return Instant.now().getEpochSecond();
    }


    public static Long getLongId() {
        return CommonIdGenerator.getInstance().generateId();
    }

    public static String unpack(Long id) {
        return CommonIdGenerator.getInstance().unpack(id);
    }

    public static String generateOrderId() {
        String now = new SimpleDateFormat("yyMMdd").format(new Date());
        return now + CommonIdGenerator.getInstance().generateId();
    }
}
